// socketstream.h: interface for the socketstream class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_SOCKETSTREAM_H__FB53D601_A3AF_448B_8637_C410C7F13428__INCLUDED_)
#define AFX_SOCKETSTREAM_H__FB53D601_A3AF_448B_8637_C410C7F13428__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "Socket.h"
#include <streambuf>

using namespace std;

// ------------------------------------------------------
// --- this class is supposed to serve as a stream buffer 
// --- associated with a socket
// ------------------------------------------------------
template <class charT, class traits = char_traits<charT> >
class TCPStreamBuffer : public basic_streambuf<charT, traits>
{
	typedef basic_streambuf<charT, traits>	sbuf_type;
	typedef typename sbuf_type::int_type	int_type;
	typedef charT				char_type;
		
	enum{ BUFSIZE_DEFAULT = 2048};
	
public:
	  
	  // --- the buffer will take ownership of the socket 
	  // --- (ie. it will close it
	  // --- in the destructor) if takeowner == true
	  // Constructor-1
	explicit TCPStreamBuffer (Socket&	sock,bool takeowner = false, streamsize	bufsize = BUFSIZE_DEFAULT)
		:rsocket_(sock), 
		ownsocket_(takeowner),
		inbuf_(NULL), 
		outbuf_(NULL), 
		bufsize_(bufsize),
		remained_(0), 
		ownbuffers_(false)
	{
	};
	
	  // Destructor
	~TCPStreamBuffer()
	{
		if ((rsocket_.state() == Socket::CONNECTED) ||
			(rsocket_.state() == Socket::ACCEPTED))
		{
			_flush();
		}
		
		if (ownbuffers_)
		{
			delete [] inbuf_;
			delete [] outbuf_;
		}
		
		if (ownsocket_) 
			rsocket_.Close();		
	};
	  
	  
protected:
	basic_streambuf<charT, traits>*	setbuf(char_type *s, streamsize n)
	{
		if (gptr() == NULL)
		{
			setg (s, s + n, s + n);
			setp (s, s + n);
			inbuf_	= s;
			outbuf_	= s;
			bufsize_	= n;
			ownbuffers_	= false;
		}
		
		return this;
	};
	  
	void	_flush()
	{
		rsocket_.SendBytes(outbuf_, (pptr() - outbuf_) * sizeof(char_type));
	};
	
	int_type	overflow(int_type c = traits::eof())
	{
		// this method is supposed to flush the put area of the buffer
		// to the I/O device
		
		// if the buffer was not already allocated nor set by user,
		// do it just now
		if (pptr() == NULL)
		{
			outbuf_ = new char_type[bufsize_];
			ownbuffers_ = true;
		}
		else
		{
			_flush();
		}
		
		setp(outbuf_, outbuf_ + bufsize_);
		if (c != traits::eof())
		{
			sputc(traits::to_char_type(c));
		}
		return 0;	
	};
	
	int		sync()
	{
		_flush();
		setp (outbuf_, outbuf_ + bufsize_);
		return 0;
	};
	  
	int_type	underflow()
	{
		// this method is supposed to read some bytes from the I/O device
		
		// if the buffer was not already allocated nor set by user,
		// do it just now
		if (gptr() == NULL)
		{
			inbuf_ = new char_type[bufsize_];
			ownbuffers_ = true;
		}
		
		if (remained_ != 0)
			inbuf_[0] = remainedchar_;
		
		size_t readn = rsocket_.read (
			static_cast<char*>(inbuf_) + remained_,
			bufsize_ * sizeof(char_type) - remained_
			);
		
		// if (readn == 0 && remained_ != 0)
		// error - there is not enough bytes for completing
		// the last character before the end of the stream
		// - this can mean error on the remote end
		
		if (readn == 0)  return traits::eof();
		
		
		size_t totalbytes = readn + remained_;
		setg (inbuf_, inbuf_, inbuf_ + totalbytes / sizeof(char_type));
		
		remained_ = totalbytes % sizeof(char_type);
		if (remained_ != 0)
		{
			remainedchar_ = inbuf_[totalbytes / sizeof(char_type)];
		}
		
		return sgetc();		
	}
	
private:
	  
	  // --- not for use
	TCPStreamBuffer(const TCPStreamBuffer& );
	TCPStreamBuffer& operator=(const TCPStreamBuffer& );
	
	Socket&	rsocket_;
	bool		ownsocket_;
	char_type*		inbuf_;
	char_type*		outbuf_;
	streamsize		bufsize_;
	size_t		remained_;
	char_type		remainedchar_;
	bool		ownbuffers_;
	  
}; // TCPStreamBuffer


// -------------------------------------------------------------
// --- this class is an ultimate stream associated with a socket
// -------------------------------------------------------------
template <class charT, class traits = char_traits<charT> >
class TCPGenericStream :
	private	TCPStreamBuffer<charT, traits>,
	public	basic_iostream<charT, traits>
{
public:

    // --- this constructor takes 
    // --- 'ownership' of the socket wrapper if btakeowner == true,
    // --- so that the socket will be closed in the destructor of the
    // --- TCPStreamBuffer object
    // Constructor-1
    explicit TCPGenericStream (	Socket&	sock, bool takeowner = false)
		:TCPStreamBuffer<charT, traits>(sock, takeowner),
		basic_iostream<charT, traits>(this)
	{
		
	};
	
private:
    // not for use

    // Copy Constructor
    TCPGenericStream(const TCPGenericStream& );
	
    TCPGenericStream& operator=(const TCPGenericStream& );
};

// -----------------------------------------------------
// --- this is even more specialized for use as a client
// -----------------------------------------------------
template <class charT, class traits = char_traits<charT> >
class TCPGenericClientStream :
	private	SocketClient, // means "implemented in terms of ... "
	public	TCPGenericStream<charT, traits>
{
  public:

    // Constructor-1
    TCPGenericClientStream(const char *address, int port)
		:SocketClient(address,port),
		 TCPGenericStream<charT, traits>(*this, false)
	{
		// the following operation is the default action of SocketClient(address,port)
		//SocketClient::ReconnectServer(); 
	};

    ~TCPGenericClientStream()
	{
	};	// for testing only
	
	TCPGenericClientStream()
		:SocketClient(),
		 TCPGenericStream<charT, traits>(*this, false)
	{
	}

	inline int Open(const std::string& host, int port)
	{
		return SocketClient::Open(host, port);
	}
	inline int ReconnectServer()
	{
		return SocketClient::ReconnectServer();
	}
	void Close()
	{
		SocketClient::Close();
	}

  private:
    // --- not for use

    // Copy Constructor
    TCPGenericClientStream(const TCPGenericClientStream& );

    TCPGenericClientStream& operator=(const TCPGenericClientStream& );
};

// ---------------------------------------------------
// --- helper declarations for streams
// ---------------------------------------------------

typedef TCPGenericClientStream<char>	TCPClientStream;


#endif // !defined(AFX_SOCKETSTREAM_H__FB53D601_A3AF_448B_8637_C410C7F13428__INCLUDED_)
